/**
 * Copyright (c) 2010-2020 by skzr.org
 * All rights reserved.
 * @Author <a href="mailto:skzr.org@gmail.com">skzr.org</a>
 * @date 2011-3-15 下午11:00:38
 */
package org.skzr.logging.impl;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.skzr.logging.stereotype.BaseName;
import org.skzr.logging.util.IResource;
import org.skzr.logging.util.UtilSlf4jLocal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.skzr.logging.util.UtilSlf4jLocal.getResources;
import static org.skzr.logging.util.UtilSlf4jLocal.isEmptyStr;

/**
 * BaseName对应的本地化存储
 * @author <a href="mailto:skzr.org@gmail.com">skzr.org</a>
 * @version 1.0.0
 * @since JDK1.6
 */
public final class MessageProperties {
	private static final transient Logger LOGGER = LoggerFactory.getLogger(MessageProperties.class);
	private final transient Map<Locale, Map<String, String>> messageCache = new HashMap<Locale, Map<String, String>>();
	
	/**
	 * 寻找本地化数据
	 * @return List&lt;IResource&gt;
	 */
	private static List<IResource> findResource(Class<?> clazz, String baseValue) {
		List<IResource> propertiesFiles = new ArrayList<IResource>();
		if (baseValue.indexOf('.') == -1) {	//使用了简写直接在类包中搜索
			propertiesFiles.addAll(getResources(clazz.getClassLoader(), clazz.getPackage().getName() + "." + baseValue));
			//如果找不到，继续在默认地方寻找
			if (propertiesFiles.isEmpty()) propertiesFiles.addAll(getResources(clazz.getClassLoader(), baseValue));
		} else {
			propertiesFiles.addAll(getResources(clazz.getClassLoader(), baseValue));
		}
		return propertiesFiles;
	}
	/**
	 * 根据文件名，提取代表的Locale
	 * @param msgName 本地化 名称
	 * @param fileName 本地化文件名
	 * @return 本地化信息
	 */
	private static Locale getLocale(String msgName, String fileName) {
		int pos = fileName.lastIndexOf('.');
		if (pos == -1 || !fileName.startsWith(msgName)) {
			throw new IllegalArgumentException("[LOG I10N]logger class[" + msgName + "], fileName[" + fileName + "]");
		}
		
		String[] tmp = fileName.substring(0, pos).split("_");
		if (tmp.length < 1 || tmp.length > 4 || !msgName.equalsIgnoreCase(tmp[0])) {
			throw new IllegalArgumentException("[LOG I10N]logger class[" + msgName + "], fileName[" + fileName + "]");
		}
		return tmp.length == 1 ? null
				: tmp.length == 2 ? new Locale(tmp[1])
					: tmp.length == 3 ? new Locale(tmp[1], tmp[2]) 
							: new Locale(tmp[1], tmp[2], tmp[3]);
	}

	/**
	 * 加载属性文件
	 * @param resource
	 * @param charset
	 * @return 本地化属性对
	 */
	private static Map<String, String> loadProperties(IResource resource, String charset) {
		Properties properties = new Properties();
		InputStreamReader reader = null;
		try {
			reader = new InputStreamReader(resource.getInputStream(), charset);
			properties.load(reader);
			
			Map<String, String> propMap = new HashMap<String, String>(properties.size());
			for (Entry<Object, Object> entry : properties.entrySet()) {
				propMap.put(entry.getKey().toString(), entry.getValue().toString());
			}
			return propMap;
		} catch (IOException e) {
			LOGGER.warn("[Developer][I10N-Configuration issues]load failed: " + resource + ";charset is " + charset, e);
		} finally {
			UtilSlf4jLocal.close(reader);
		}
		return null;
	}
	
	/**
	 * 初始化本地化数据
	 * @param clazz 枚举类
	 * @return 本地化数据
	 */
	public static MessageProperties initMessageProperties(Class<?> clazz) { // NOPMD by skzrorg on 11-3-22 上午12:58
		BaseName baseName = clazz.getAnnotation(BaseName.class);
		final String baseValue = baseName == null || isEmptyStr(baseName.value()) ? clazz.getSimpleName() : baseName.value().trim();
		
		int pos = baseValue.lastIndexOf('.');
		final String msgName = pos == -1 ? baseValue : baseValue.substring(pos + 1);
		
		final String key = clazz.getName(); 
		final String charset = baseName == null || isEmptyStr(baseName.charset()) ? "UTF-8" : baseName.charset().trim();
		Map<Locale, Map<String, String>> tmp = new HashMap<Locale, Map<String, String>>();
		for (IResource resource : findResource(clazz, baseValue)) {
			Locale locale = getLocale(msgName, resource.getFileName());
			if (tmp.containsKey(locale)) {
				LOGGER.warn("[Developer][I10N-Configuration issues]properties name[{}] already exists [Locale={}] I10N，Ignoring: {}",
						new Object[]{key, locale, resource});
				continue;
			}
			tmp.put(locale, loadProperties(resource, charset));
		}
		return new MessageProperties(tmp);
	}
	
	
	private MessageProperties(Map<Locale, Map<String, String>> messages) {
		messageCache.putAll(messages);
		for (Locale locale : messages.keySet()) {	//向下扩展本地locale
			if (locale != null) {
				Locale country = new Locale(locale.getLanguage(), locale.getCountry());
				if (!messageCache.containsKey(country)) messageCache.put(country, messageCache.get(locale));
				
				Locale language = new Locale(locale.getLanguage());
				if (!messageCache.containsKey(language)) {
					messageCache.put(language, 
							messageCache.containsKey(country) ? messageCache.get(country)
									: messageCache.get(locale));
				}
			}
		}
	}
	
	private Map<String, String> getProperties(Locale locale) {
		Map<String, String> properties = messageCache.get(locale);
		if (properties != null || locale == null) return properties;
		
		//扩展本地locale
		Locale country = new Locale(locale.getLanguage(), locale.getCountry());
		if (messageCache.containsKey(country)) {
			messageCache.put(locale, messageCache.get(country));
		} else {
			Locale language = new Locale(locale.getLanguage());
			if (messageCache.containsKey(language)) {
				messageCache.put(locale, messageCache.get(language));
			}
		}
		return messageCache.get(locale);
	}
	public <E extends Enum<?>> String findMessage(E key, Locale locale) {
		if (key == null) return null;
		
		final String msgKey = key.name();
		
		String text = null;
		Map<String, String> properties = getProperties(locale);
		if (properties != null) {
			if (properties.containsKey(msgKey)) return properties.get(msgKey);
			text = properties.get(msgKey);
		}
		
		if (locale != null) {
			properties = getProperties(null);
			if (properties != null) text = properties.get(msgKey);
		}
		return isEmptyStr(text) ? null : text.trim(); // NOPMD by skzrorg on 11-3-22 上午12:57
	}
}
